package com.piesat.feaedit.feaedit.service.impl;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.northpool.commons.util.ZipUtils;
import com.northpool.resources.Constants;
import com.northpool.resources.datasource.IDataSource;
import com.northpool.resources.datasource.db.DbDataSource;
import com.northpool.resources.datasource.ogr.KMLDataSource;
import com.northpool.resources.datasource.ogr.ShapeDataSource;
import com.northpool.resources.datatable.FieldEncoder;
import com.northpool.resources.datatable.ITable;
import com.northpool.resources.datatable.SpatialInfo;
import com.northpool.resources.datatable.bean.TableMetaStatistics;
import com.northpool.spatial.Geom;
import com.northpool.stpclient.StpClient;
import com.northpool.stpclient.bean.BaseResponse;
import com.northpool.stpclient.bean.datatransfer.HandlerClassParams;
import com.northpool.stpclient.bean.datatransfer.TableCopyBuildPlan;
import com.northpool.stpclient.modules.geoprocess.datatransfer.IDataTransferOperator;
import com.northpool.stpclient.task.StpServerTaskExecutor;
import com.piesat.feaedit.feaedit.base.exception.BusinessException;
import com.piesat.feaedit.feaedit.bean.FeatureBean;
import com.piesat.feaedit.feaedit.bean.request.CreateServerRequest;
import com.piesat.feaedit.feaedit.bean.request.ExportStatus;
import com.piesat.feaedit.feaedit.bean.request.MapserviceEntity;
import com.piesat.feaedit.feaedit.bean.request.RegisterDataRequest;
import com.piesat.feaedit.feaedit.bean.request.SaveStyleRequest;
import com.piesat.feaedit.feaedit.bean.request.ServiceStatus;
import com.piesat.feaedit.feaedit.bean.request.StyleEntity;
import com.piesat.feaedit.feaedit.bean.request.UpdateFeatureRequest;
import com.piesat.feaedit.feaedit.config.ProjectConfig;
import com.piesat.feaedit.feaedit.config.StpConfig;
import com.piesat.feaedit.feaedit.service.IFeatureService;
import org.apache.commons.io.FileUtils;
import org.gdal.ogr.DataSource;
import org.gdal.ogr.StyleTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class FeatureServiceImpl implements IFeatureService {
    final static Logger logger = LoggerFactory.getLogger(FeatureServiceImpl.class);

    final StpConfig stpConfig;
    final ProjectConfig projectConfig;

    final StpClient stpClient;
    final DbDataSource targetDb;

    Map<String, ServiceStatus> statusMap = new ConcurrentHashMap<>();
    Map<String, ExportStatus> exportStatusMap = new ConcurrentHashMap<>();


    public FeatureServiceImpl(StpConfig stpConfig, ProjectConfig projectConfig) {
        this.stpConfig = stpConfig;
        this.projectConfig = projectConfig;
        StpServerTaskExecutor.statusInterval = 500;
        stpClient = new StpClient(stpConfig.getUrl());
        targetDb = new DbDataSource("target", projectConfig.getDataSourceUrl(), projectConfig.getUserName(), projectConfig.getPassword(), Constants.DATA_SOURCE_TYPE.postgreSQL);

    }


    @Override
    public void dataEntry(File file, String tableName) throws Exception {
        if (targetDb.hasTable(tableName)) {
           throw new BusinessException("已存在表");
        }
        String layerName = null;
        String uploadDir = projectConfig.getUploadDir();
        UUID uuid = UUID.randomUUID();
        File tmpDir = new File(uploadDir + File.separator + uuid);

        tmpDir.mkdir();
        List<HandlerClassParams> handlerList = new LinkedList<>();

        String strokeColor = "";
        String strokeWidth = "1";
        String strokeOpacity = "1";
        String fillColor = "#4C5250";
        String fillOpacity = "1";
        List<ShapeDataSource> inputSources = new ArrayList<>();
        if (file.getName().endsWith(".kml")) {
//            PEN(c:#000000FF,w:1.000000px);BRUSH(fc:#000000BF)
            String name = file.getName();
            layerName = name.substring(0, name.length() - 4);
            String pathname = tmpDir.getAbsolutePath() + File.separator + name;
            FileUtils.moveFile(file, new File(pathname));
            File[] files = this.kml2shp(pathname, tmpDir.getAbsolutePath());
            inputSources = Arrays.stream(files).map(r->new ShapeDataSource("source", r.getAbsolutePath())).collect(Collectors.toList());
            try {
                KMLDataSource kmlDataSource = new KMLDataSource("source", pathname);
                DataSource ogrDs = kmlDataSource.connection().getReadShell().getDataSource();
                StyleTable styleTable = ogrDs.GetStyleTable();
                if (styleTable != null) {
                    String style = styleTable.GetNextStyle();
                    // 正则表达式模式
                    Pattern penPattern = Pattern.compile("PEN\\(c:(#\\w+),w:(\\d+\\.\\d+)px\\)");
                    Pattern brushPattern = Pattern.compile("BRUSH\\(fc:(#\\w+)\\)");
                    // 匹配PEN的颜色和宽度
                    Matcher penMatcher = penPattern.matcher(style);
                    if (penMatcher.find()) {
                        String rgba = penMatcher.group(1);
                        String penColor = rgba.substring(0, 7);
                        String penOpacity = rgba.substring(7, 9);
                        String penWidth = penMatcher.group(2);
                        strokeColor = penColor;
                        strokeWidth = String.valueOf(Float.parseFloat(penWidth));
                        strokeOpacity = String.valueOf(hexToFloat(penOpacity));

                        System.out.println("PEN Color: " + strokeColor);
                        System.out.println("PEN Width: " + strokeWidth);
                        System.out.println("PEN penOpacity: " + strokeOpacity);
                    }

                    // 匹配BRUSH的填充颜色
                    Matcher brushMatcher = brushPattern.matcher(style);
                    if (brushMatcher.find()) {
                        String rgba = brushMatcher.group(1);
                        String brushColor = rgba.substring(0, 7);
                        String brushOpacity = rgba.substring(7, 9);
                        fillColor = brushColor;
                        fillOpacity = String.valueOf(hexToFloat(brushOpacity));
                        System.out.println("BRUSH Fill Color: " + fillColor);
                        System.out.println("BRUSH Fill penOpacity: " + fillOpacity);
                    }
                }
            }catch (Exception ex) {
                logger.error("kml样式解析失败: {}",ex.getMessage());
            }



//            source = kmlDataSource;
//            //debug
//            HandlerClassParams handlerClassParams = new HandlerClassParams();
//            HandlerClassParams handlerClassParams2 = new HandlerClassParams();
//            handlerClassParams.setClazz("com.northpool.stpprocess.transfer.operator.GeometryCollection2Polygon");
//            handlerClassParams2.setClazz("com.northpool.stpprocess.transfer.operator.GeometryDimensionReduceTransform");
//            handlerList.add(handlerClassParams);
//            handlerList.add(handlerClassParams2);
        }else {
            try {
                FileUtils.createParentDirectories(tmpDir);
                ZipUtils.unzip(file, tmpDir);
            } catch (IOException e) {
                throw new BusinessException("解压失败");
            }
            // 读取文件名
            File[] files = tmpDir.listFiles((dir, name) -> name.endsWith(".shp"));
            File shpFile = files[0];
//            String name = shpFile.getName();
//            layerName = name.substring(0, name.length() - 4);
            ShapeDataSource source = new ShapeDataSource("source", shpFile.getAbsolutePath());
            inputSources.add(source);
        }

        for (ShapeDataSource inputSource : inputSources) {
            dataInDb(inputSource, layerName, tableName, handlerList, file, strokeColor, strokeOpacity, strokeWidth, fillColor, fillOpacity, inputSources.size());
            String[] tables = inputSource.tables();
            if (ArrayUtil.isEmpty(tables)) {
                throw new BusinessException("shp为空，未找到图层");
            }
            String table = tables[0];
            dataInDb(inputSource, table, tableName, handlerList, file, strokeColor, strokeOpacity, strokeWidth, fillColor, fillOpacity, inputSources.size());
        }
    }

    private void dataInDb(ShapeDataSource source,String layerName, String tableName, List<HandlerClassParams> handlerList, File file,
                          String strokeColor, String strokeOpacity, String strokeWidth, String fillColor, String fillOpacity, Integer fileNum
    ) throws UnsupportedEncodingException {

        IDataTransferOperator operator = stpClient.dataTransferOperator();
        TableCopyBuildPlan plan = new TableCopyBuildPlan();
        String _tableName = tableName;
        String _layerName = layerName;
        if (fileNum > 1) {
            String filePath = source.getFilePath();
            if (filePath.endsWith("point.shp")) {
                _tableName += "_point";
                _layerName = "point";
            }
            if (filePath.endsWith("polyline.shp")) {
                _tableName += "_line";
                _layerName = "polyline";
            }
            if (filePath.endsWith("polygon.shp")) {
                _tableName += "_polygon";
                _layerName = "polygon";
            }
        }

        plan.setOriginDatasource(source);
        plan.setOriginTableName(_layerName);
        plan.setTargetDatasource(targetDb);
        plan.setTargetTableName(_tableName);
        plan.setSrid(0);
        plan.setMiddleHandlers(handlerList);
        BaseResponse response = operator.startTableCopy(plan);

        operator.onStart(response.getRequestId(), () -> {
            logger.info("数据入库开始: {}", file.getName());
            if (!statusMap.containsKey(tableName)) {
                statusMap.put(tableName, new ServiceStatus("beginInDb"));
            }
        });

        IDataSource finalSource = source;
        operator.onSuccess(response.getRequestId(), () -> {
            String log = operator.log(response.getRequestId());
            logger.info("数据入库成功: {}", log);
            if (!statusMap.containsKey(tableName)) {
                statusMap.put(tableName, new ServiceStatus("successInDb"));
            }
            ServiceStatus status = statusMap.get(tableName);
            status.setNumber(status.getNumber() + 1);
            // 全部入库完成再发服务
            if (fileNum.equals(status.getNumber())) {
                try {
                    String[] tables = finalSource.tables();
                    TableMetaStatistics metaStatistics = finalSource.getTableOperator(tables[0]).getMetaStatistics(false);
                    double[] extent = metaStatistics.getExtent();
                    statusMap.get(tableName).setExtent(extent);
                } catch (Exception e) {
                    logger.error("数据统计失败: {}", tableName, e);
                }

                try {
                    publishServer(tableName,
                            strokeColor, strokeWidth, strokeOpacity, fillColor, fillOpacity);
                } catch (Exception e) {
                    logger.error("服务发布失败: {}", tableName, e);
                }
            }
        });

        operator.onError(response.getRequestId(), () -> {
            String log = operator.log(response.getRequestId());
            logger.error("数据入库失败: {}", log);
            ServiceStatus status = new ServiceStatus("failed");
            status.setMsg("数据入库失败");
            statusMap.put(tableName, status);
        });

    }

    @Override
    public FeatureBean findFeature(String tableName, Double lng, Double lat) {
        float buffer = 0.0001F;
        Integer srid = 0;
        String sql = String.format("select * from \"public\".\"%s\" WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint(%f, %f), %d), %s) limit 1", tableName, lng, lat, srid, buffer);
        List<Map<String, Object>> maps = targetDb.queryBySql(sql, new Object[0]);
        Optional<Map<String, Object>> first = maps.stream().findFirst();
        FeatureBean feature = new FeatureBean();
        Map<String, Object> props = new HashMap<>();
        if (!first.isPresent()) {
            return null;
        } else {
            Map<String, Object> result = first.get();
            for (String field : result.keySet()) {
                Object val = result.get(field);
                if (val instanceof Geom) {
                    Geom geom = (Geom) val;
                    feature.setWkt(geom.toWkt());
                }else {
                    props.put(field, val);
                }
            }
            feature.setProps(props);
            feature.setId(props.get("fid").toString());
        }
        return feature;
    }

    @Override
    public Object queryTask(String taskId, String type) {
        if("export".equals(type)) {
            return this.exportStatusMap.get(taskId);
        }else {
            return this.statusMap.get(taskId);
        }
    }

    @Override
    public void updateFeature(UpdateFeatureRequest request) {
        String tableName = request.getTableName();
        String wkt = request.getWkt();
        String key = request.getKey().toString();

        Integer srid = 0;
        Geom geom = new Geom(com.northpool.spatial.Constants.SPATIAL_TYPE.wkt, wkt);
        String _tableName = tableName;
        if (geom.toJTSGeometry().getGeometryType().toLowerCase().equals("point")) {
            _tableName += "_point";
        } else if(geom.toJTSGeometry().getGeometryType().toLowerCase().equals("line")) {
            _tableName += "_line";
        } else if(geom.toJTSGeometry().getGeometryType().toLowerCase().equals("polygon")) {
            _tableName += "_polygon";
        }
        String sql = String.format("update \"public\".\"%s\" set geom = ST_GeomFromText('%s', %d) where fid = '%s'", _tableName, wkt, srid, key);
        List<Object> objects = targetDb.executeSql(sql);
        logger.debug("更新数据: {}", key);
    }

    public File[] kml2shp(String inputFile, String outputDir) {
        try {
            // 构造命令数组
            // 判断是否为Windows
            String osName = System.getProperty("os.name").toLowerCase();
            boolean isWindows = osName.contains("windows");

            String exe = isWindows ? "kml2shp.exe" : "./kml2shp";
            String[] command = {exe, "-i", inputFile, "-o", outputDir};
            logger.info("执行命令: {}", Arrays.toString(command));
            // 创建ProcessBuilder对象
            ProcessBuilder pb = new ProcessBuilder(command);
            // 启动进程
            Process process = pb.start();
            // 获取命令执行结果的输入流
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            // 读取并打印每行输出
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            // 等待命令执行完成
            int exitVal = process.waitFor();
            if (exitVal == 0) {
                System.out.println("Command executed successfully.");
                File dir = new File(outputDir);
                return dir.listFiles(r->r.getName().endsWith(".shp"));
            } else {
                throw new BusinessException("kml转换shp失败");
            }
        } catch (IOException | InterruptedException e) {
            logger.error(e.getMessage(), e);
            throw new BusinessException("kml转换shp命令执行失败");
        }
    }

    public String publishDataService(String tableName, String keyFieldName, String geomFieldName, String fields) {
        logger.info("发布数据服务: {}", tableName);
        RegisterDataRequest dataRequest = new RegisterDataRequest();
        dataRequest.setTableName("public." + tableName);
        dataRequest.setKeyField(keyFieldName);
        dataRequest.setShapeField(geomFieldName);
        dataRequest.setFields(fields);
        dataRequest.setAlias(tableName);
        dataRequest.setName(tableName);
        dataRequest.setDatasourceId(projectConfig.getDataSourceId());
        String dataRequestJson = "[" + JSON.toJSONString(dataRequest) + "]";
        String resultData = HttpRequest.post(projectConfig.getDataServiceUrl() + "/apis/dataservice/register")
                .header("token", "-1")
                .body(dataRequestJson, "application/json")
                .execute().body();
        logger.info(resultData);
        JSONObject resultDataObj = JSONObject.parseObject(resultData);
        Integer status = resultDataObj.getInteger("status");
        if(!status.equals(200)) {
            logger.error("数据服务发布失败:{}", resultDataObj.getString("msg"));
            throw new RuntimeException("数据服务发布异常");
        }
        JSONArray dataArr = resultDataObj.getJSONArray("data");
        String dataId = dataArr.getString(0);
        return dataId;
    }
    @Override
    public ServiceStatus publishServer(String tableName, String strokeColor, String strokeWidth, String strokeOpacity, String fillColor, String fillOpacity) throws IOException {
        if (!statusMap.containsKey(tableName)) {
            statusMap.put(tableName, new ServiceStatus());
        }

        ServiceStatus task = statusMap.get(tableName);
        List<String> tableList = this.getTableList(tableName);
        List<String> dataIds = new ArrayList<>();
        String geometryType = "unkown";
        // 1. 发布数据服务 (点、线、面)
        if(tableList.size() > 1) {
            for (String subTable : tableList) {
                ITable table;
                try {
                    table = targetDb.getTable(subTable, FieldEncoder.ORIGIN_FIELD_ENCODER);
                } catch (Exception e) {
                    throw new BusinessException("数据表获取失败：" + e.getMessage());
                }
                SpatialInfo spatial = table.spatialInfo();
                geometryType = spatial.getGeometryType().name().toLowerCase();
                String keyField = table.getIdField();
                String fields = ArrayUtil.join(table.getFields(), ",");
                String geomFieldName = spatial.getOriginFieldName();

                String dataId = publishDataService(subTable, keyField, geomFieldName, fields);
                dataIds.add(dataId);
            }
        }else {
            ITable table;
            try {
                table = targetDb.getTable(tableName, FieldEncoder.ORIGIN_FIELD_ENCODER);
            } catch (Exception e) {
                throw new BusinessException("数据表获取失败：" + e.getMessage());
            }
            SpatialInfo spatial = table.spatialInfo();

            geometryType = spatial.getGeometryType().name().toLowerCase();
            String keyField = table.getIdField();
            String fields = ArrayUtil.join(table.getFields(), ",");
            String geomFieldName = spatial.getOriginFieldName();

            String dataId = publishDataService(tableName, keyField, geomFieldName, fields);
            dataIds.add(dataId);
        }

//        String geometryType = spatialInfo.getGeometryType().toString().toLowerCase();
        logger.info("数据服务发布成功: {}", tableList.toArray());
        task.setStatus("publishDataService");

        //2. 发布矢量服务
        logger.info("发布矢量服务: {}", tableName);
        String style;
        if (tableList.size() == 1) {
            if(geometryType.contains("point")) {
                style = FileUtils.readFileToString(new File(projectConfig.getStyleDir() + File.separator + "style_point.json"));
                style = style.replaceAll("\\{\\{name\\}\\}", tableName);
            }else if(geometryType.contains("line")) {
                style = FileUtils.readFileToString(new File(projectConfig.getStyleDir() + File.separator + "style_line.json"));
                style = style.replaceAll("\\{\\{name\\}\\}", tableName);
            }else if(geometryType.contains("polygon")) {
                style = FileUtils.readFileToString(new File(projectConfig.getStyleDir() + File.separator + "style_polygon.json"));
                style = style.replaceAll("\\{\\{name\\}\\}", tableName);
                style = style.replaceAll("\\{\\{strokeColor\\}\\}", strokeColor);
                style = style.replaceAll("\\{\\{strokeWidth\\}\\}", strokeWidth);
                style = style.replaceAll("\\{\\{strokeOpacity\\}\\}", strokeOpacity);
                style = style.replaceAll("\\{\\{fillColor\\}\\}", fillColor);
                style = style.replaceAll("\\{\\{fillOpacity\\}\\}", fillOpacity);
            } else {
                throw new RuntimeException("几何类型暂不支持");
            }
        } else {
            style = FileUtils.readFileToString(new File(projectConfig.getStyleDir() + File.separator + "style_all.json"));
            style = style.replaceAll("\\{\\{name\\}\\}", tableName);
            style = style.replaceAll("\\{\\{strokeColor\\}\\}", strokeColor);
            style = style.replaceAll("\\{\\{strokeWidth\\}\\}", strokeWidth);
            style = style.replaceAll("\\{\\{strokeOpacity\\}\\}", strokeOpacity);
            style = style.replaceAll("\\{\\{fillColor\\}\\}", fillColor);
            style = style.replaceAll("\\{\\{fillOpacity\\}\\}", fillOpacity);
        }

        CreateServerRequest createRequest = new CreateServerRequest();
        createRequest.setDataIds(dataIds);
        createRequest.setStyle(new StyleEntity("custom_style", style));
        createRequest.setMapserviceEntity(new MapserviceEntity(tableName, tableName));

        String resultCreate = HttpRequest.post(projectConfig.getDataServiceUrl() + "/apis/services/create")
                .header("token", "-1")
                .body(JSON.toJSONString(createRequest), "application/json")
                .execute().body();

        logger.info(resultCreate);
        JSONObject resultCreteObj = JSONObject.parseObject(resultCreate);
        Integer createStatus = resultCreteObj.getInteger("status");
        if(!createStatus.equals(200)) {
            logger.error("矢量服务发布失败:{}", resultCreteObj.getString("msg"));
            throw new RuntimeException("矢量服务发布异常");
        }

        //3.保存样式
        logger.info("保存服务样式: {}", tableName);
        SaveStyleRequest styleRequest = new SaveStyleRequest();
        styleRequest.setServiceId(tableName);
        styleRequest.setStyleId("custom_style");
        styleRequest.setStyleName("custom_style");
        styleRequest.setStyleContent(style);

        String resultStyle = HttpRequest.post(projectConfig.getDataServiceUrl() + "/apis/style/create")
                .header("token", "-1")
                .body(JSON.toJSONString(styleRequest), "application/json")
                .execute().body();

        logger.info(resultStyle);
        JSONObject resultStyleObj = JSONObject.parseObject(resultStyle);
        if(!resultStyleObj.getInteger("status").equals(200)) {
            logger.error("样式保存失败:{}", resultStyleObj.getString("msg"));
            throw new RuntimeException("样式保存异常");
        }

        task.setStatus("success");
        task.setServiceName(tableName);
        task.setStyleId("custom_style");
        task.setTableName(tableName);
        return task;
    }

    @Override
    public void deleteAllData(String tableName) {
        String sql = String.format("TRUNCATE TABLE \"public\".\"%s\" CASCADE", tableName);
        List<Object> objects = targetDb.executeSql(sql);
        logger.info("清空数据: {}", tableName);
    }

    private List<String> getTableList(String tableName) {
        // targetDb 是否存在多张表
        Boolean hasTable = targetDb.hasTable(tableName);
        List<String> exportTables = new ArrayList<>();
        if(!hasTable) {
            if (targetDb.hasTable(tableName + "_point")) {
                exportTables.add(tableName + "_point");
            }
            if(targetDb.hasTable(tableName + "_line")) {
                exportTables.add(tableName + "_line");
            }
            if(targetDb.hasTable(tableName + "_polygon")) {
                exportTables.add(tableName + "_polygon");
            }
        }else {
            exportTables.add(tableName);
        }
        return exportTables;
    }
    @Override
    public void exportShp(String tableName) throws UnsupportedEncodingException {
        ExportStatus exportStatus = new ExportStatus();
        this.exportStatusMap.put(tableName, exportStatus);
        List<String> exportTables = this.getTableList(tableName);
        UUID uuid = UUID.randomUUID();
        exportStatus.setStatus("begin");
        for (String exportTable : exportTables) {
            this.exportOneShp(exportTable, uuid.toString(), exportStatus, exportTables.size());
        }
    }

    private void exportOneShp(String tableName, String uuid, ExportStatus exportStatus, Integer fileNum) throws UnsupportedEncodingException {
        // 确保父级文件夹存在
        File parentDir = new File(projectConfig.getUploadDir() + File.separator + uuid);
        parentDir.mkdir();
        File shpFile = new File(projectConfig.getUploadDir() + File.separator + uuid + File.separator + tableName + ".shp");

        IDataTransferOperator operator = stpClient.dataTransferOperator();
        TableCopyBuildPlan plan = new TableCopyBuildPlan();
        ShapeDataSource target = new ShapeDataSource("source", shpFile.getAbsolutePath());

        plan.setOriginDatasource(targetDb);
        plan.setOriginTableName("public." + tableName);
        plan.setTargetDatasource(target);
        plan.setTargetTableName(tableName);
        plan.setSrid(4326);
        BaseResponse response = operator.startTableCopy(plan);
        operator.onError(response.getRequestId(), () -> {
            exportStatus.setStatus("error");
            String log = operator.log(response.getRequestId());
            logger.error("数据导出失败: {}", log);
        });
        operator.onSuccess(response.getRequestId(), () -> {
            exportStatus.setNumber(exportStatus.getNumber() + 1);
            if (fileNum.equals(exportStatus.getNumber())) {
                logger.info("shp导出成功");
                File zipFile = new File(projectConfig.getUploadDir() + File.separator + uuid + File.separator + tableName + ".zip");
                if (zipFile.exists()) {
                    zipFile.delete();
                }
                File[] files = shpFile.getParentFile().listFiles();
                ZipUtil.zip(zipFile, false, files);

                String absolutePath = zipFile.getAbsolutePath();
                exportStatus.setUrl(absolutePath);
                exportStatus.setStatus("success");
            }
        });
    }

    @Override
    public void deleteFeature(String tableName, String featureId) {
        String sql = String.format("delete from \"public\".\"%s\" where fid = '%s'", tableName, featureId);
        List<Object> objects = targetDb.executeSql(sql);
        logger.info("删除要素: {}", featureId);
    }

    @Override
    public FileSystemResource download(String tableName) {
        ExportStatus exportStatus = this.exportStatusMap.get(tableName);
        if (exportStatus == null) {
           throw new BusinessException("未找到导出任务");
        }
        if ("success".equals(exportStatus.getStatus())) {
            return  new FileSystemResource(new File(exportStatus.getUrl()));
        } else {
            throw new BusinessException("数据导出任务未完成");
        }
    }

    /**
     * 将十六进制透明度转换为0到1之间的浮点数
     * @param alphaHex 十六进制透明度（单个字符）
     * @return 0到1之间的浮点数
     */
    private static double hexToFloat(String alphaHex) {
        int alphaInt = Integer.parseInt(alphaHex, 16);
        return alphaInt / 255.0;
    }

}
